/**
 * 注册自己的文件系统
 * myfs_system.c
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>

#include <linux/fs.h>
#include <linux/pagemap.h>
#include <asm/atomic.h>
#include <asm/uaccess.h>

#include <linux/blkdev.h>

#define MYFS_MAGIC 0x20120426

static struct inode *myfs_make_inode(struct super_block *bs, int mode)
{
	struct inode *ret = new_inode(bs);

	if (ret) {
		ret->i_mode = mode;
		ret->i_uid = ret->i_gid = 0;
		//ret->i_blkbits = PAGE_CACHE_SIZE;
		ret->i_blkbits = blksize_bits(PAGE_CACHE_SIZE);
		ret->i_blocks = 0;
		ret->i_atime = ret->i_mtime = ret->i_ctime = CURRENT_TIME;
	}

	return ret;
}

static int myfs_open(struct inode *inode, struct file *filp)
{
	//filp->private_data = inode->u.generic_ip;
	filp->private_data = inode->i_private;
	return 0;
}

#define TMPSIZE 20

static ssize_t myfs_read(struct file *filp, char *buf, size_t count, loff_t *offset)
{
	atomic_t *counter = (atomic_t *)filp->private_data;
	int v, len;
	char tmp[TMPSIZE];

	v = atomic_read(counter);
	if (*offset > 0)
		v -= 1;
	else
		atomic_inc(counter);
	len = snprintf(tmp, TMPSIZE, "%d\n", v);
	if (*offset > len)
		return 0;
	if (count > len - *offset)
		count = len - *offset;

	if (copy_to_user(buf, tmp + *offset, count))
		return -EFAULT;
	*offset += count;

	return count;
}

static ssize_t myfs_write(struct file *filp, const char *buf, size_t count, loff_t *offset)
{
	atomic_t *counter = (atomic_t*)filp->private_data;
	char tmp[TMPSIZE];

	if (*offset != 0)
		return -EINVAL;

	if (count >= TMPSIZE)
		return -EINVAL;
	memset(tmp, 0x0, TMPSIZE);
	if (copy_from_user(tmp, buf, count))
		return -EFAULT;

	atomic_set(counter, simple_strtol(tmp, NULL, 10));

	return count;
}

static struct file_operations mkfs_file_ops = {
	.open = myfs_open,
	.read = myfs_read,
	.write = myfs_write,
};

static struct dentry *myfs_create_file(struct super_block *sb, struct dentry *dir, const char *name, atomic_t *counter)
{
	struct dentry *dentry;
	struct inode *inode;
	struct qstr qname;

	qname.name = name;
	qname.len = strlen(name);
	qname.hash = full_name_hash(name, qname.len);

	dentry = d_alloc(dir, &qname);
	if (!dentry) {
		goto out;
	}

	inode = myfs_make_inode(sb, S_IFREG | 0644);
	if (!inode) {
		goto out_dput;
	}
	inode->i_fop = &mkfs_file_ops;
	inode->i_private = counter;

	d_add(dentry, inode);

	return dentry;

out_dput:
	dput(dentry);
out:
	return 0;
}

static struct dentry *myfs_create_dir(struct super_block *sb, struct dentry *parent, const char *name)
{
	struct dentry *dentry;
	struct inode *inode;
	struct qstr qname;

	qname.name = name;
	qname.len = strlen(name);
	qname.hash = full_name_hash(name, qname.len);
	dentry = d_alloc(parent, &qname);
	if (!dentry) {
		goto out;
	}

	inode = myfs_make_inode(sb, S_IFDIR | 0644);
	if (!inode) {
		goto out_dput;
	}
	inode->i_op = &simple_dir_inode_operations;
	inode->i_fop = &simple_dir_operations;

	d_add(dentry, inode);

	return dentry;

out_dput:
	dput(dentry);
out:
	return 0;
}

static atomic_t counter, subcounter;

static void myfs_create_files(struct super_block *sb, struct dentry *root)
{
	struct dentry *subdir;

	atomic_set(&counter, 0);
	myfs_create_file(sb, root, "counter", &counter);

	atomic_set(&subcounter, 0);
	subdir = myfs_create_dir(sb, root, "subdir");
	if (subdir) {
		myfs_create_file(sb, subdir, "subconter", &subcounter);
	}
}

static struct super_operations myfs_s_ops = {
	.statfs = simple_statfs,
	.drop_inode = generic_delete_inode,
};

/*fill a superblock with mundane stuff*/
static int myfs_fill_super(struct super_block *sb, void *data, int silent)
{
	struct inode *root;
	struct dentry *root_dentry;
	
	sb->s_blocksize = PAGE_CACHE_SIZE;
	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
	sb->s_magic = MYFS_MAGIC;
	sb->s_op = &myfs_s_ops;

	root = myfs_make_inode(sb, S_IFDIR | 0755);
	if (!root) {
		goto out;
	}
	root->i_op = &simple_dir_inode_operations;
	root->i_fop = &simple_dir_operations;

	root_dentry = d_alloc_root(root);
	if (!root_dentry) {
		goto out_iput;
	}
	sb->s_root = root_dentry;

	myfs_create_files(sb, root_dentry);
	
	return 0;

out_iput:
	iput(root);
out:
	return -ENOMEM;
}

static int myfs_get_sb(struct file_system_type *myfs, int flags, const char *devname, void *data, struct vfsmount *mnt)
{
	return get_sb_single(myfs, flags, data, myfs_fill_super, mnt);
}

static void myfs_kill_super(struct super_block *sb)
{
	return ;
}

static struct file_system_type myfs_type = {
	.owner = THIS_MODULE,
	.name = "myfs",
	.get_sb = myfs_get_sb,
	.kill_sb = myfs_kill_super,
};

static int __init myfs_system_init(void)
{
	return register_filesystem(&myfs_type);
}

static void __exit myfs_system_exit(void)
{
	unregister_filesystem(&myfs_type);
}

module_init(myfs_system_init);
module_exit(myfs_system_exit);
